File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BNG {
[ExecuteInEditMode]
public class HandPoser : MonoBehaviour {
public bool ShowGizmos = true;
[Tooltip("Path of the directory where handposes should be stored. Tip : Keep these in a 'Resources' directory so you can use Resources.Load().")]
public string ResourcePath = "Assets/BNG Framework/HandPoser/Poses/Resources/";
public string PoseName = "Default";
[Tooltip("The currently selected hand pose. Change this to automatically update the pose in Update")]
public HandPose CurrentPose;
[Header("Animation Properties")]
[Tooltip("The speed at which to lerp the bones when changing hand poses")]
public float AnimationSpeed = 15f;
[Tooltip("If true the local rotation of each bone will be updated while changing hand poses. This should generally be true if you are adjusting a hand pose.")]
public bool UpdateJointRotations = true;
[Tooltip("If true the local position of each bone will be updated while changing hand poses. Typically this will be false as joints only adjust their rotations.")]
public bool UpdateJointPositions = false;
[Tooltip("If true the local position of the wrist will be updated. Useful if you need to move the entire hand.")]
public bool UpdateWristPosition = true;
// Hand Pose Transform Definitions
public HandPoseDefinition HandPoseJoints {
get {
return GetHandPoseDefinition();
}
}
public Transform WristJoint;
public List<Transform> ThumbJoints;
public List<Transform> IndexJoints;
public List<Transform> MiddleJoints;
public List<Transform> RingJoints;
public List<Transform> PinkyJoints;
public List<Transform> OtherJoints;
HandPose previousPose;
bool doSingleAnimation;
// Continuously update pose state
public bool ContinuousUpdate = false;
void Start() {
// Trigger a pose change to start the animation
OnPoseChanged();
}
// This is also run in the editor
void Update() {
// Check for pose change event
CheckForPoseChange();
// Lerp to Hand Pose
if (ContinuousUpdate || doSingleAnimation) {
DoPoseUpdate();
}
}
public void CheckForPoseChange() {
if (previousPose == null || (CurrentPose != null && previousPose != null && previousPose.name != CurrentPose.name && CurrentPose != null)) {
OnPoseChanged();
previousPose = CurrentPose;
}
}
public void OnPoseChanged() {
// Allow pose to change animation
editorAnimationTime = 0;
doSingleAnimation = true;
}
public FingerJoint GetWristJoint() {
return GetJointFromTransform(WristJoint);
}
public List<FingerJoint> GetThumbJoints() {
return GetJointsFromTransforms(ThumbJoints);
}
public List<FingerJoint> GetIndexJoints() {
return GetJointsFromTransforms(IndexJoints);
}
public List<FingerJoint> GetMiddleJoints() {
return GetJointsFromTransforms(MiddleJoints);
}
public List<FingerJoint> GetRingJoints() {
return GetJointsFromTransforms(RingJoints);
}
public List<FingerJoint> GetPinkyJoints() {
return GetJointsFromTransforms(PinkyJoints);
}
public List<FingerJoint> GetOtherJoints() {
return GetJointsFromTransforms(OtherJoints);
}
public int GetTotalJointsCount() {
if(ThumbJoints == null || IndexJoints == null) {
return 0;
}
return ThumbJoints.Count + IndexJoints.Count + MiddleJoints.Count + RingJoints.Count + PinkyJoints.Count + OtherJoints.Count + (WristJoint != null ? 1 : 0);
}
public Transform GetTip(List<Transform> transforms) {
if(transforms != null) {
return transforms[transforms.Count - 1];
}
return null;
}
public Transform GetThumbTip() { return GetTip(ThumbJoints); }
public Transform GetIndexTip() { return GetTip(IndexJoints); }
public Transform GetMiddleTip() { return GetTip(MiddleJoints); }
public Transform GetRingTip() { return GetTip(RingJoints); }
public Transform GetPinkyTip() { return GetTip(PinkyJoints); }
public static Vector3 GetFingerTipPositionWithOffset(List<Transform> jointTransforms, float tipRadius) {
if(jointTransforms == null || jointTransforms.Count == 0) {
return Vector3.zero;
}
// Not available
if(jointTransforms[jointTransforms.Count - 1] == null) {
return Vector3.zero;
}
Vector3 tipPosition = jointTransforms[jointTransforms.Count - 1].position;
if(jointTransforms.Count == 1) {
return tipPosition;
}
return tipPosition + (jointTransforms[jointTransforms.Count - 2].position - tipPosition).normalized * tipRadius;
}
public virtual List<FingerJoint> GetJointsFromTransforms(List<Transform> jointTransforms) {
List<FingerJoint> joints = new List<FingerJoint>();
// Check for empty joints
if(jointTransforms == null) {
return joints;
}
// Add any joint information to the list
for (int x = 0; x < jointTransforms.Count; x++) {
if(jointTransforms[x] != null) {
joints.Add(GetJointFromTransform(jointTransforms[x]));
}
}
return joints;
}
public virtual FingerJoint GetJointFromTransform(Transform jointTransform) {
// Null check
if(jointTransform == null) {
return null;
}
return new FingerJoint() {
TransformName = jointTransform.name,
LocalPosition = jointTransform.localPosition,
LocalRotation = jointTransform.localRotation
};
}
public virtual void UpdateHandPose(HandPose handPose, bool lerp) {
UpdateHandPose(handPose.Joints, lerp);
}
public virtual void UpdateHandPose(HandPoseDefinition pose, bool lerp) {
UpdateJoint(pose.WristJoint, WristJoint, lerp, UpdateWristPosition, UpdateJointRotations);
UpdateJoints(pose.ThumbJoints, ThumbJoints, lerp);
UpdateJoints(pose.IndexJoints, IndexJoints, lerp);
UpdateJoints(pose.MiddleJoints, MiddleJoints, lerp);
UpdateJoints(pose.RingJoints, RingJoints, lerp);
UpdateJoints(pose.PinkyJoints, PinkyJoints, lerp);
UpdateJoints(pose.OtherJoints, OtherJoints, lerp);
}
public virtual void UpdateJoint(FingerJoint fromJoint, Transform toTransform, bool doLerp, bool updatePosition, bool updateRotation) {
UpdateJoint(fromJoint, toTransform, doLerp ? Time.deltaTime * AnimationSpeed : 1, updatePosition, updateRotation);
}
public virtual void UpdateJoint(FingerJoint fromJoint, Transform toTransform, float lerpAmount, bool updatePosition, bool updateRotation) {
// Invalid joint
if (fromJoint == null || toTransform == null) {
return;
}
// Lerp
if (lerpAmount != 1) {
if(updatePosition) {
toTransform.localPosition = Vector3.Lerp(toTransform.localPosition, fromJoint.LocalPosition, lerpAmount);
}
if(UpdateJointRotations) {
toTransform.localRotation = Quaternion.Lerp(toTransform.localRotation, fromJoint.LocalRotation, lerpAmount);
}
}
// Instantaneous can directly set the local position and rotation
else {
if (UpdateJointPositions) {
toTransform.localPosition = fromJoint.LocalPosition;
}
if (UpdateJointRotations) {
toTransform.localRotation = fromJoint.LocalRotation;
}
}
}
public virtual void UpdateJoints(List<FingerJoint> joints, List<Transform> toTransforms, bool doLerp) {
UpdateJoints(joints, toTransforms, doLerp ? Time.deltaTime * AnimationSpeed : 1);
}
public virtual void UpdateJoints(List<FingerJoint> joints, List<Transform> toTransforms, float lerpAmount) {
// Sanity check
if (joints == null || toTransforms == null) {
return;
}
// Cache the count of our lists
int jointCount = joints.Count;
int transformsCount = toTransforms.Count;
// If our joint counts don't add up, then make sure the names match before applying any changes
// Otherwise there is a good chance the wwrong transforms are being updated
bool verifyTransformName = jointCount != transformsCount;
for (int x = 0; x < jointCount; x++) {
// Make sure the indexes match
if (x < toTransforms.Count) {
// Joint may have not been assigned or destroyed
if (joints[x] == null || toTransforms[x] == null) {
continue;
}
if (verifyTransformName && joints[x].TransformName == toTransforms[x].name) {
UpdateJoint(joints[x], toTransforms[x], lerpAmount, UpdateJointPositions, UpdateJointRotations);
}
else if (verifyTransformName == false) {
UpdateJoint(joints[x], toTransforms[x], lerpAmount, UpdateJointPositions, UpdateJointRotations);
}
}
}
}
public virtual HandPoseDefinition GetHandPoseDefinition() {
return new HandPoseDefinition() {
WristJoint = GetWristJoint(),
ThumbJoints = GetThumbJoints(),
IndexJoints = GetIndexJoints(),
MiddleJoints = GetMiddleJoints(),
RingJoints = GetRingJoints(),
PinkyJoints = GetPinkyJoints(),
OtherJoints = GetOtherJoints()
};
}
/// <summary>
/// Saves the current hand configuration as a Unity Scriptable object
/// Pose will be saved to the provided 'HandPosePath' (Typically within in a Resources folder)
/// *Scriptable Objects can only be saved from within the UnityEditor.
/// </summary>
/// <param name="poseName"></param>
/// <returns></returns>
public virtual void SavePoseAsScriptablObject(string poseName) {
#if UNITY_EDITOR
string fileName = poseName + ".asset";
var poseObject = GetHandPoseScriptableObject();
// Creates the file in the folder path
//string fullPath = Application.dataPath + directory + fileName;
string fullPath = ResourcePath + fileName;
bool exists = System.IO.File.Exists(fullPath);
// Delete if asset already exists
if (exists) {
UnityEditor.AssetDatabase.DeleteAsset(fullPath);
}
UnityEditor.AssetDatabase.CreateAsset(poseObject, fullPath);
UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.AssetDatabase.Refresh(UnityEditor.ImportAssetOptions.ForceUpdate);
UnityEditor.EditorUtility.SetDirty(poseObject);
if (exists) {
Debug.Log("Updated Hand Pose : " + poseName);
}
else {
Debug.Log("Created new Hand Pose : " + poseName);
}
#else
Debug.Log("Scriptable Objects can only be saved from within the Unity Editor. Consider storing in another format like JSON instead.");
#endif
}
/// <summary>
/// Will create an original handpose with the specified PoseName. If the provided poseName is already taken, a number will be added to the name until a unique name is found.
/// For example, if you provide the PoseName of "Default" and there are already two poses named "Default" and "Default 1", then a new pose named "Default 2" will be created.
/// </summary>
/// <param name="poseName"></param>
public virtual void CreateUniquePose(string poseName) {
// Don't allow empty pose names
if(string.IsNullOrEmpty(poseName)) {
poseName = "Pose";
}
string formattedPoseName = poseName;
string fullPath = ResourcePath + formattedPoseName + ".asset";
bool exists = System.IO.File.Exists(fullPath);
int checkCount = 0;
// Find a path that doesn't exist
while(exists) {
// Ex : "path/Pose 5.asset"
formattedPoseName = poseName + " " + checkCount;
exists = System.IO.File.Exists(ResourcePath + formattedPoseName + ".asset");
checkCount++;
}
// Save the new pose
SavePoseAsScriptablObject(formattedPoseName);
}
public virtual HandPose GetHandPoseScriptableObject() {
#if UNITY_EDITOR
var poseObject = UnityEditor.Editor.CreateInstance<HandPose>();
poseObject.PoseName = PoseName;
poseObject.Joints = new HandPoseDefinition();
poseObject.Joints.WristJoint = GetWristJoint();
poseObject.Joints.ThumbJoints = GetThumbJoints();
poseObject.Joints.IndexJoints = GetIndexJoints();
poseObject.Joints.MiddleJoints = GetMiddleJoints();
poseObject.Joints.RingJoints = GetRingJoints();
poseObject.Joints.PinkyJoints = GetPinkyJoints();
poseObject.Joints.OtherJoints = GetOtherJoints();
return poseObject;
#else
return null;
#endif
}
// How long to check for animations while in the editor mode
float editorAnimationTime = 0f;
float maxEditorAnimationTime = 2f;
public virtual void DoPoseUpdate() {
if (CurrentPose != null) {
UpdateHandPose(CurrentPose.Joints, true);
}
// Are we done requesting a single animation?
if (doSingleAnimation) {
editorAnimationTime += Time.deltaTime;
// Reset
if (editorAnimationTime >= maxEditorAnimationTime) {
editorAnimationTime = 0;
doSingleAnimation = false;
}
}
}
public virtual void ResetEditorHandles() {
EditorHandle[] handles = GetComponentsInChildren<EditorHandle>();
for (int x = 0; x < handles.Length; x++) {
if(handles[x] != null && handles[x].gameObject != null) {
GameObject.DestroyImmediate((handles[x]));
}
}
}
void OnDrawGizmos() {
#if UNITY_EDITOR
// Update every frame even while in editor
if (!Application.isPlaying) {
UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
UnityEditor.SceneView.RepaintAll();
}
#endif
}
}
}